#include "../pch.h"
#include "core/REFXMLTextEncoder.h"

namespace BFX
{

REFXMLWriter::REFXMLWriter()
{
	m_pWriteNodeBuffer = NULL;
}


REFXMLWriter::~REFXMLWriter()
{
}

//////////////////////////////////////////////////////////////////////////////
// Copied from Microsoft .NET framework.

const REFXMLTextWriter::State REFXMLTextWriter::s_pStateTableDefault[] = {
	//                        StateStart      StateProlog     StatePostDTD    StateElement    StateAttribute  StateContent   StateAttrOnly   StateEpilog
	//
	/* TokenPI             */ StateProlog,    StateProlog,    StatePostDTD,   StateContent,   StateContent,   StateContent,  StateError,     StateEpilog,
	/* TokenDoctype        */ StatePostDTD,   StatePostDTD,   StateError,     StateError,     StateError,     StateError,    StateError,     StateError,
	/* TokenComment        */ StateProlog,    StateProlog,    StatePostDTD,   StateContent,   StateContent,   StateContent,  StateError,     StateEpilog,
	/* TokenCData          */ StateContent,   StateContent,   StateError,     StateContent,   StateContent,   StateContent,  StateError,     StateEpilog,
	/* TokenStartElement   */ StateElement,   StateElement,   StateElement,   StateElement,   StateElement,   StateElement,  StateError,     StateElement,
	/* TokenEndElement     */ StateError,     StateError,     StateError,     StateContent,   StateContent,   StateContent,  StateError,     StateError,
	/* TokenLongEndElement */ StateError,     StateError,     StateError,     StateContent,   StateContent,   StateContent,  StateError,     StateError,
	/* TokenStartAttribute */ StateAttrOnly,  StateError,     StateError,     StateAttribute, StateAttribute, StateError,    StateError,     StateError,
	/* TokenEndAttribute   */ StateError,     StateError,     StateError,     StateError,     StateElement,   StateError,    StateEpilog,     StateError,
	/* TokenContent        */ StateContent,   StateContent,   StateError,     StateContent,   StateAttribute, StateContent,  StateAttribute, StateEpilog,
	/* TokenBase64         */ StateContent,   StateContent,   StateError,     StateContent,   StateAttribute, StateContent,  StateAttribute, StateEpilog,
	/* TokenRawData        */ StateProlog,    StateProlog,    StatePostDTD,   StateContent,   StateAttribute, StateContent,  StateAttribute, StateEpilog,
	/* TokenWhitespace     */ StateProlog,    StateProlog,    StatePostDTD,   StateContent,   StateAttribute, StateContent,  StateAttribute, StateEpilog,
};

const REFXMLTextWriter::State REFXMLTextWriter::s_pStateTableDocument[] = {
	//                        StateStart      StateProlog     StatePostDTD    StateElement    StateAttribute  StateContent   StateAttrOnly   StateEpilog
	//
	/* TokenPI             */ StateError,     StateProlog,    StatePostDTD,   StateContent,   StateContent,   StateContent,  StateError,     StateEpilog,
	/* TokenDoctype        */ StateError,     StatePostDTD,   StateError,     StateError,     StateError,     StateError,    StateError,     StateError,
	/* TokenComment        */ StateError,     StateProlog,    StatePostDTD,   StateContent,   StateContent,   StateContent,  StateError,     StateEpilog,
	/* TokenCData          */ StateError,     StateError,     StateError,     StateContent,   StateContent,   StateContent,  StateError,     StateError,
	/* TokenStartElement   */ StateError,     StateElement,   StateElement,   StateElement,   StateElement,   StateElement,  StateError,     StateError,
	/* TokenEndElement     */ StateError,     StateError,     StateError,     StateContent,   StateContent,   StateContent,  StateError,     StateError,
	/* TokenLongEndElement */ StateError,     StateError,     StateError,     StateContent,   StateContent,   StateContent,  StateError,     StateError,
	/* TokenStartAttribute */ StateError,     StateError,     StateError,     StateAttribute, StateAttribute, StateError,    StateError,     StateError,
	/* TokenEndAttribute   */ StateError,     StateError,     StateError,     StateError,     StateElement,   StateError,    StateError,     StateError,
	/* TokenContent        */ StateError,     StateError,     StateError,     StateContent,   StateAttribute, StateContent,  StateError,     StateError,
	/* TokenBase64         */ StateError,     StateError,     StateError,     StateContent,   StateAttribute, StateContent,  StateError,     StateError,
	/* TokenRawData        */ StateError,     StateProlog,    StatePostDTD,   StateContent,   StateAttribute, StateContent,  StateError,     StateEpilog,
	/* TokenWhitespace     */ StateError,     StateProlog,    StatePostDTD,   StateContent,   StateAttribute, StateContent,  StateError,     StateEpilog,
};

REFXMLTextWriter::REFXMLTextWriter(REFUniTextWriter* pWriter)
{
	BFX_REQUIRE (pWriter != NULL);

	m_pStack = NULL;
	m_pWriter = pWriter;
	m_pXmlEncoder = new REFXMLTextEncoder(pWriter);
	m_pXmlEncoder->AddRef();

	m_pStateTable = s_pStateTableDefault;
	m_currentState = StateStart;
	m_lastToken = TokenEmpty;
}

REFXMLTextWriter::~REFXMLTextWriter()
{
	m_pXmlEncoder->Release();
}

// Closes any open elements or attributes and puts the writer back in the Start state.
void REFXMLTextWriter::WriteEndDocument()
{
	BFX_ENSURE1 (m_currentState == StateEpilog, "Document does not have a root element.");
	AutoCompleteAll();

	m_pStateTable = s_pStateTableDefault;
	m_currentState = StateStart;
	m_lastToken = TokenEmpty;
}

// Writes out the DOCTYPE declaration with the specified name and optional attributes.
void REFXMLTextWriter::WriteDocType(const UniString& strName, const UniString& strPubid, const UniString& strSysid, const UniString& strSubset)
{
	// TODO:
	BFX_ASSERT(false);
}

// Writes out the specified start tag and associates it with the given namespace and prefix.
void REFXMLTextWriter::WriteStartElement(const UniString& strPrefix, const UniString& strLocalName, const UniString& strNs)
{
	AutoComplete(TokenStartElement);
	PushStack();
	m_pWriter->Write('<');
	// TODO: Process namespace, prefix etc.

	m_pStack->m_strName = strLocalName;
	m_pWriter->Write(strLocalName);
}

// Closes one element and pops the corresponding namespace scope.
void REFXMLTextWriter::WriteEndElement()
{
	WriteEndElement(false);
}

// Closes one element and pops the corresponding namespace scope.
void REFXMLTextWriter::WriteFullEndElement()
{
	WriteEndElement(true);
}

void REFXMLTextWriter::WriteEndElement(bool bLongFormat)
{
	BFX_ENSURE1 (m_pStack != NULL, "There was no XML start tag open.");

	// if we are in the element, we need to close it.
	AutoComplete(bLongFormat ? TokenLongEndElement : TokenEndElement);
	if (m_lastToken == TokenLongEndElement)
	{
		// TODO: Indent
		m_pWriter->Write('<');
		m_pWriter->Write('/');
		// TODO: prefix
		m_pWriter->Write(m_pStack->m_strName);
		m_pWriter->Write('>');
	}
	// TODO: pop namespaces

	// PopStack
	m_pStack = m_pStack->m_pNext;
}

// Writes the start of an attribute.
void REFXMLTextWriter::WriteStartAttribute(const UniString& strPrefix, const UniString& strLocalName, const UniString& strNs)
{
	AutoComplete(TokenStartAttribute);
	// TODO: Namespace

	// TODO: special attribute

	m_pXmlEncoder->StartAttribute();

	m_pWriter->Write(strLocalName);
	m_pWriter->Write('=');
	m_pWriter->Write('"');
}

// Closes the attribute opened by WriteStartAttribute.
void REFXMLTextWriter::WriteEndAttribute()
{
	AutoComplete(TokenEndAttribute);
}

// Writes out a <![CDATA[...]]>; block containing the specified text.
void REFXMLTextWriter::WriteCData(const UniString& strText)
{
	// TODO:
	BFX_ASSERT(false);
}

// Writes out a comment <!--...-->; containing the specified text.
void REFXMLTextWriter::WriteComment(const UniString& strText)
{
	// TODO:
	BFX_ASSERT(false);
}

// Writes out an entity reference as follows: "&"+name+";".
void REFXMLTextWriter::WriteEntityRef(const UniString& strName)
{
	// TODO:
	BFX_ASSERT(false);
}

// Forces the generation of a character entity for the specified Unicode character value.
void REFXMLTextWriter::WriteCharEntity(UCHAR ch)
{
	// TODO:
	BFX_ASSERT(false);
}

// Writes out the given whitespace.
void REFXMLTextWriter::WriteWhitespace(const UniString& strWs)
{
	// TODO:
	BFX_ASSERT(false);
}

void REFXMLTextWriter::StartDocument(int nStandalone)
{
	BFX_ENSURE1 (m_currentState == StateStart, "WriteStartDocument needs to be the first call.");
	m_pStateTable = s_pStateTableDocument;
	m_currentState = StateProlog;

	UniString buf;
	buf.Append(USTR("version=\"1.0\""));
	// TODO: encoding.
	if (nStandalone >= 0)
	{
		buf.Append(USTR(" standalone=\""));
		buf.Append(USTR(nStandalone == 0 ? "yes" : "no"));
		buf.Append('"');
	}

	WriteProcessingInstruction(USTR("xml"), buf);
}

// Writes out a processing instruction with a space between the name and text as follows: <?name text?>
void REFXMLTextWriter::WriteProcessingInstruction(const UniString& strName, const UniString& strText)
{
	AutoComplete(TokenPI);

	m_pWriter->Write(USTR("<?"));
	m_pWriter->Write(strName);
	m_pWriter->Write(' ');
	if (strText.IsEmpty())
	{
		// TODO: check text before write.
		m_pWriter->Write(strText);
	}
	m_pWriter->Write(USTR("?>"));
}

void REFXMLTextWriter::WriteEndStartTag(bool bEmpty)
{
	// TODO: Namespaces

	if (bEmpty)
	{
		m_pWriter->Write(USTR(" /"));
	}
	m_pWriter->Write('>');
}

// Writes out the specified text content.
void REFXMLTextWriter::WriteString(const UniString& strText)
{
	if (!strText.IsEmpty())
	{
		AutoComplete(TokenContent);
		m_pXmlEncoder->Write(strText);
	}
}

// Returns the state of the XmlWriter.
REFXMLWriter::WriteState REFXMLTextWriter::GetWriteState() const
{
	switch (m_currentState)
	{
	case StateStart :
		return WriteStateStart;
	case StateProlog :
	case StatePostDTD :
		return WriteStateProlog;
	case StateElement :
		return WriteStateElement;
	case StateAttribute :
	case StateAttrOnly:
		return WriteStateAttribute;
	case StateContent :
	case StateEpilog :
		return WriteStateContent;
	case StateError:
		return WriteStateError;
	case StateClosed:
		return WriteStateClosed;
	default:
		BFX_ASSERT(false);
		return WriteStateError;
	}
}

// Closes the XmlWriter
void REFXMLTextWriter::Close()
{
	m_pWriter->Close();
}

// Flushes data that is in the internal buffers
void REFXMLTextWriter::Flush()
{
	m_pWriter->Flush();
}

REFUniTextWriter* REFXMLTextWriter::GetTextWriter() const
{
	return m_pWriter;
}

void REFXMLTextWriter::AutoComplete(Token token)
{
	BFX_ENSURE1 (m_currentState != StateClosed, "The Writer is closed.");
	BFX_ENSURE1 (m_currentState != StateError, "Token in state would result in an invalid XML document.");

	State newState = m_pStateTable[(int)token * 8 + (int)m_currentState];
	BFX_ENSURE1 (newState != StateError, "Token in state would result in an invalid XML document.");

	switch (token)
	{
	case TokenDoctype:
		// TODO: indent???
		break;

	case TokenStartElement:
	case TokenComment:
	case TokenPI:
	case TokenCData:
		if (m_currentState == StateAttribute)
		{
			WriteEndAttributeQuote();
			WriteEndStartTag(false);
		}
		else if (m_currentState == StateElement)
		{
			WriteEndStartTag(false);
		}
		// TODO: indent???
		break;

	case TokenEndElement:
	case TokenLongEndElement:
		// TODO: flush???
		if (m_currentState == StateAttribute)
		{
			WriteEndAttributeQuote();
		}
		if (m_currentState == StateContent)
		{
			token = TokenLongEndElement;
		}
		else
		{
			WriteEndStartTag(token == TokenEndElement);
		}
		break;

	case TokenStartAttribute:
		// TODO: flush???
		if (m_currentState == StateAttribute)
		{
			WriteEndAttributeQuote();
			m_pWriter->Write(' ');
		}
		else if (m_currentState == StateElement)
		{
			m_pWriter->Write(' ');
		}
		break;

	case TokenEndAttribute:
		// TODO: flush???
		WriteEndAttributeQuote();
		break;

	case TokenWhitespace:
	case TokenContent:
	case TokenRawData:
	case TokenBase64:
		// TODO: flush???
		if (m_currentState == StateElement && m_lastToken != TokenContent)
		{
			WriteEndStartTag(false);
		}
		// TODO: stack
		break;

	default:
		BFX_ASSERT (0);
	}
	m_currentState = newState;
	m_lastToken = token;
}

void REFXMLTextWriter::AutoCompleteAll()
{
	while (m_pStack != NULL)
	{
		WriteEndElement();
	}
}

void REFXMLTextWriter::WriteEndAttributeQuote()
{
	// TODO: Handle special attribute

	m_pXmlEncoder->EndAttribute();
	m_pWriter->Write('"');
}

void REFXMLTextWriter::PushStack()
{
	TagInfo* p = new TagInfo();
	p->m_pNext = m_pStack;
	m_pStack = p;
}

}	//	namespace BFX
